﻿using System;
using System.Collections.Generic;
using System.Linq;
using MarketDataAnalyzer;
using NUnit.Framework;

namespace Tests.Unit
{
    [TestFixture]
    public class WhenCreatingPriceAnalysis
    {
        private const string ProjectedSymbol1 = "FOO";
        private const string ProjectedSymbol2 = "BAR";

        private DateTime newEventTime = new DateTime( 2011, 01, 01, 09, 30, 00 );
        private DateTime projectedEventTime1 = new DateTime( 2010, 10, 01, 09, 30, 00 );
        private DateTime projectedEventTime2 = new DateTime( 2010, 11, 01, 09, 30, 00 );

        private PriceAnalysis priceAnalysis;

        [Test]
        public void GeneratingPricePointsWorksCorrectly()
        {
            const decimal eventPrice = 10.00m;
            
            var risingPricePoints = GenerateRisingPricePoints( newEventTime, eventPrice );
            Assert.That( risingPricePoints.First().Price, Is.LessThan( risingPricePoints.Last().Price ) );
            Assert.That(risingPricePoints.Count(), Is.EqualTo( 121 )  );
            var eventPriceFromList = risingPricePoints.Where( x => x.Time == newEventTime ).Select( x => x.Price ).First();
            Assert.That(eventPriceFromList, Is.EqualTo( eventPrice )  );
            
            var fallingPricePoints = GenerateFallingPricePoints( newEventTime, eventPrice );
            Assert.That( fallingPricePoints.First().Price, Is.GreaterThan( fallingPricePoints.Last().Price ) );
            Assert.That(fallingPricePoints.Count(), Is.EqualTo(121));
            eventPriceFromList = fallingPricePoints.Where(x => x.Time == newEventTime).Select(x => x.Price).First();
            Assert.That(eventPriceFromList, Is.EqualTo( eventPrice )  );
        }

        [Test]
        public void GetActualPriceFunctionsProperly()
        {
            const decimal eventPrice = 10.00m;
            var pricePoints = GenerateRisingPricePoints(newEventTime, eventPrice);
            priceAnalysis = new PriceAnalysis
                                {
                                    ActualPricePoints = pricePoints,
                                    NewsEventTimeEst = newEventTime
                                };

            Assert.That(priceAnalysis.GetActualPriceAtRelativeTime(0), Is.EqualTo( eventPrice )  );
            Assert.That( priceAnalysis.GetActualPriceAtRelativeTime(-60), Is.EqualTo( eventPrice - 0.60m ) );
            Assert.That( priceAnalysis.GetActualPriceAtRelativeTime(60), Is.EqualTo( eventPrice + 0.60m ) );

        }

        [Test]
        public void GetActualChangeInPercentWorksOnRisingPrice()
        {
            const decimal eventPrice = 10.00m;
            var pricePoints = GenerateRisingPricePoints(newEventTime, eventPrice);
            priceAnalysis = new PriceAnalysis
                                {
                                    ActualPricePoints = pricePoints,
                                    NewsEventTimeEst = newEventTime
                                };

            Assert.That( priceAnalysis.GetActualChangeInPercentAfterEvent( 60 ), Is.Not.EqualTo( 0 ) );
            const decimal percentChangeAfterSixtyMinutes = .60m / eventPrice;
            Assert.That( priceAnalysis.GetActualChangeInPercentAfterEvent(60), Is.EqualTo( percentChangeAfterSixtyMinutes ) );
        }

        [Test]
        public void GetActualChangeInPercentWorksOnFallingPrice()
        {
            const decimal eventPrice = 10.00m;
            var pricePoints = GenerateFallingPricePoints(newEventTime, eventPrice);
            priceAnalysis = new PriceAnalysis
            {
                ActualPricePoints = pricePoints,
                NewsEventTimeEst = newEventTime
            };

            Assert.That(priceAnalysis.GetActualChangeInPercentAfterEvent(60), Is.Not.EqualTo(0));
            const decimal percentChangeAfterSixtyMinutes = -0.60m / eventPrice;
            Assert.That(priceAnalysis.GetActualChangeInPercentAfterEvent(60), Is.EqualTo(percentChangeAfterSixtyMinutes));
        }

        [Test]
        public void GetProjectedChangeWorksForRising()
        {
            const decimal projectedPriceAtEvent1 = 25.00m;
            const decimal projectedPriceAtEvent2 = 50.00m;
            var projectedPriceAnalysis1 = new ProjectedPriceAnalysis
                                              {
                                                  NewsEventTimeEst = projectedEventTime1,
                                                  PricePoints =
                                                      GenerateRisingPricePoints( projectedEventTime1,
                                                                                 projectedPriceAtEvent1 ),
                                                  Symbol = ProjectedSymbol1
                                              };
            var projectedPriceAnalysis2 = new ProjectedPriceAnalysis
                                              {
                                                  NewsEventTimeEst = projectedEventTime2,
                                                  PricePoints =
                                                      GenerateRisingPricePoints( projectedEventTime2,
                                                                                 projectedPriceAtEvent2 ),
                                                  Symbol = ProjectedSymbol2
                                              };
            var projectedPriceAnalyses = new List<ProjectedPriceAnalysis>
                                             {
                                                 projectedPriceAnalysis1,
                                                 projectedPriceAnalysis2
                                             };
            priceAnalysis = new PriceAnalysis
                                {
                                    ProjectedPriceAnalyses = projectedPriceAnalyses
                                };

            const decimal percentageIncreaseForFirst = (0.60m / projectedPriceAtEvent1);
            const decimal percentageIncreaseForSecond = (0.60m / projectedPriceAtEvent2);
            const decimal expectedPercentageIncrease = (percentageIncreaseForFirst + percentageIncreaseForSecond) / 2.0m;

            Assert.That( priceAnalysis.GetProjectedChangeInPercent( 60 ), Is.EqualTo( expectedPercentageIncrease ) );
        }

        [Test]
        public void GetProjectedChangeWorksForFalling()
        {
            const decimal projectedPriceAtEvent1 = 25.00m;
            const decimal projectedPriceAtEvent2 = 50.00m;
            var projectedPriceAnalysis1 = new ProjectedPriceAnalysis
                                              {
                                                  NewsEventTimeEst = projectedEventTime1,
                                                  PricePoints =
                                                      GenerateFallingPricePoints( projectedEventTime1,
                                                                                  projectedPriceAtEvent1 ),
                                                  Symbol = ProjectedSymbol1
                                              };
            var projectedPriceAnalysis2 = new ProjectedPriceAnalysis
                                              {
                                                  NewsEventTimeEst = projectedEventTime2,
                                                  PricePoints =
                                                      GenerateFallingPricePoints( projectedEventTime2,
                                                                                  projectedPriceAtEvent2 ),
                                                  Symbol = ProjectedSymbol2
                                              };
            var projectedPriceAnalyses = new List<ProjectedPriceAnalysis>
                                             {
                                                 projectedPriceAnalysis1,
                                                 projectedPriceAnalysis2
                                             };
            priceAnalysis = new PriceAnalysis
                                {
                                    ProjectedPriceAnalyses = projectedPriceAnalyses
                                };

            const decimal percentageIncreaseForFirst = (-0.60m / projectedPriceAtEvent1);
            const decimal percentageIncreaseForSecond = (-0.60m / projectedPriceAtEvent2);
            const decimal expectedPercentageIncrease = (percentageIncreaseForFirst + percentageIncreaseForSecond) / 2.0m;

            Assert.That( priceAnalysis.GetProjectedChangeInPercent( 60 ), Is.EqualTo( expectedPercentageIncrease ) );
        }

        [Test]
        public void GetProjectedChangeWorksForOpposing()
        {
            const decimal projectedPriceAtEvent1 = 25.00m;
            const decimal projectedPriceAtEvent2 = 50.00m;
            var projectedPriceAnalysis1 = new ProjectedPriceAnalysis
                                              {
                                                  NewsEventTimeEst = projectedEventTime1,
                                                  PricePoints =
                                                      GenerateRisingPricePoints( projectedEventTime1,
                                                                                 projectedPriceAtEvent1 ),
                                                  Symbol = ProjectedSymbol1
                                              };
            var projectedPriceAnalysis2 = new ProjectedPriceAnalysis
                                              {
                                                  NewsEventTimeEst = projectedEventTime2,
                                                  PricePoints =
                                                      GenerateFallingPricePoints( projectedEventTime2,
                                                                                  projectedPriceAtEvent2 ),
                                                  Symbol = ProjectedSymbol2
                                              };
            var projectedPriceAnalyses = new List<ProjectedPriceAnalysis>
                                             {
                                                 projectedPriceAnalysis1,
                                                 projectedPriceAnalysis2
                                             };
            priceAnalysis = new PriceAnalysis
                                {
                                    ProjectedPriceAnalyses = projectedPriceAnalyses
                                };

            const decimal percentageIncreaseForFirst = (0.60m / projectedPriceAtEvent1);
            const decimal percentageIncreaseForSecond = (-0.60m / projectedPriceAtEvent2);
            const decimal expectedPercentageIncrease = (percentageIncreaseForFirst + percentageIncreaseForSecond) / 2.0m;

            Assert.That( priceAnalysis.GetProjectedChangeInPercent( 60 ), Is.EqualTo( expectedPercentageIncrease ) );
        }

        [Test]
        public void GetProjectedChangeWorksForEquallyOpposing()
        {
            const decimal projectedPriceAtEvent1 = 25.00m;
            const decimal projectedPriceAtEvent2 = 25.00m;
            var projectedPriceAnalysis1 = new ProjectedPriceAnalysis
            {
                NewsEventTimeEst = projectedEventTime1,
                PricePoints =
                    GenerateRisingPricePoints(projectedEventTime1,
                                                projectedPriceAtEvent1),
                Symbol = ProjectedSymbol1
            };
            var projectedPriceAnalysis2 = new ProjectedPriceAnalysis
            {
                NewsEventTimeEst = projectedEventTime2,
                PricePoints =
                    GenerateFallingPricePoints(projectedEventTime2,
                                                projectedPriceAtEvent2),
                Symbol = ProjectedSymbol2
            };
            var projectedPriceAnalyses = new List<ProjectedPriceAnalysis>
                                             {
                                                 projectedPriceAnalysis1,
                                                 projectedPriceAnalysis2
                                             };
            priceAnalysis = new PriceAnalysis
            {
                ProjectedPriceAnalyses = projectedPriceAnalyses
            };

            const decimal percentageIncreaseForFirst = (0.60m / projectedPriceAtEvent1);
            const decimal percentageIncreaseForSecond = (-0.60m / projectedPriceAtEvent2);
            const decimal expectedPercentageIncrease = (percentageIncreaseForFirst + percentageIncreaseForSecond) / 2.0m;

            Assert.That(priceAnalysis.GetProjectedChangeInPercent(60), Is.EqualTo(expectedPercentageIncrease));
        }


        private IEnumerable<PricePoint> GenerateRisingPricePoints(DateTime eventTime, decimal eventPrice)
        {
            var pricePoints = new List<PricePoint>();
            var endingTime = eventTime.AddMinutes( 60 );
            var currentIterationTime = eventTime.AddMinutes(-60);
            var currentIterationPrice = eventPrice - 0.60m;
            while (currentIterationTime <= endingTime)
            {
                var newPricePoint = new PricePoint
                                        {
                                            Price = currentIterationPrice,
                                            Time = currentIterationTime
                                        };
                pricePoints.Add( newPricePoint );

                currentIterationPrice += 0.01m;
                currentIterationTime = currentIterationTime.AddMinutes( 1.0 );
            }
            return pricePoints;
        }

        private IEnumerable<PricePoint> GenerateFallingPricePoints(DateTime eventTime, decimal eventPrice)
        {
            var pricePoints = new List<PricePoint>();
            var endingTime = eventTime.AddMinutes( 60 );
            var currentIterationTime = eventTime.AddMinutes(-60);
            var currentIterationPrice = eventPrice + 0.60m;
            while (currentIterationTime <= endingTime)
            {
                var newPricePoint = new PricePoint
                                        {
                                            Price = currentIterationPrice,
                                            Time = currentIterationTime
                                        };
                pricePoints.Add( newPricePoint );

                currentIterationPrice -= 0.01m;
                currentIterationTime = currentIterationTime.AddMinutes( 1.0 );
            }
            return pricePoints;
        }

    }
}
